using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using MySql.Data.MySqlClient;

namespace Cz.CzDbMySqlPool
{
    public class CzDbMySqlPool : ICzDb
    {
        [CzServiceField] private ICzLog log;
        [CzServiceField] private ICzConfig config;
        [CzServiceField] private ICzPool pool;

        private string con;
        private int initCount;
        private bool debug;
        
        public void Start()
        {
            con = config.Get<string>("mysql_pool", "con");
            initCount = config.Get<int>("mysql_pool", "init_count");
            ReLoadConfig();

            config.OnChange += ReLoadConfig;
            
            log.Info("CzDbMySqlPool init", $"init_count:{initCount}, con:{con}");
            
            pool.InitPool<MySql>(initCount, () =>
            {
                var mySqlCon = new MySqlConnection(con);
                mySqlCon.Open();

                return new MySql
                {
                    conn = mySqlCon,
                    index = MySql.INDEX++
                };
            });

            log.Info("CzDbMySqlPool start");
        }

        public void Close()
        {
            log.Info("CzDbMySQLPool Close");
            pool.ClearPool<MySql>();
        }

        public int Exec(string sql, params DbParameter[] parameters)
        {
            var mysql = pool.Get<MySql>();
            var sqlCommand = CreateCommand(mysql, sql, parameters);
            var ret = sqlCommand.ExecuteNonQuery();
            pool.Release(mysql);
            return ret;
        }

        public string ExecString(string sql, params DbParameter[] parameters)
        {
            var obj = ExecObject(sql, parameters);
            return obj?.ToString();
        }
        
        public int? ExecInt(string sql, params DbParameter[] parameters)
        {
            var obj = ExecObject(sql, parameters);
            return obj == null ? null : (int?)Convert.ToInt32(obj);
        }
        
        public object ExecObject(string sql, params DbParameter[] parameters)
        {
            var mysql = pool.Get<MySql>();
            var sqlCommand = CreateCommand(mysql, sql, parameters);
            var ret = sqlCommand.ExecuteScalar();
            pool.Release(mysql);
            return ret;
        }
        
        public Hashtable ExecRow(string sql, params DbParameter[] parameters)
        {
            var mysql = pool.Get<MySql>();
            var sqlCommand = CreateCommand(mysql, sql, parameters);
            var reader = sqlCommand.ExecuteReader();

            Hashtable ret = null;
            if (reader.Read())
            {
                ret = new Hashtable();
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    ret[reader.GetName(i)] = reader[i];
                }
            }
            reader.Close();
            pool.Release(mysql);
            return ret;
        }
        
        public List<Hashtable> ExecTable(string sql, params DbParameter[] parameters)
        {
            var mysql = pool.Get<MySql>();
            var sqlCommand = CreateCommand(mysql, sql, parameters);
            var reader = sqlCommand.ExecuteReader();
            
            var array = new LinkedList<Hashtable>();
            while (reader.Read())
            {
                var ht = new Hashtable();
                for (int i = 0; i < reader.FieldCount; i++)
                {
                    ht[reader.GetName(i)] = reader[i];
                }
                array.AddLast(ht);
            }
            reader.Close();
            pool.Release(mysql);
            
            var ret = new List<Hashtable>(array.Count);
            ret.AddRange(array);
            return ret;
        }

        public DbParameter CreatePara(string name, object val)
        {
            return new MySqlParameter(name, val);
        }
        
        private MySqlCommand CreateCommand(MySql mysql, string sql, params DbParameter[] parameters)
        {
            var sqlCommand = new MySqlCommand(sql, mysql.conn);
            sqlCommand.Parameters.AddRange(parameters);
            
            if(debug)
                log.Info($"mysql exec index {mysql.index}", sqlCommand.CommandText);
            
            return sqlCommand;
        }

        private void ReLoadConfig()
        {
            debug = config.GetWithDefault(false, "mysql_pool", "debug");
        }

        public void Dispose()
        {
            Close();
        }
        
        private class MySql : ICzPoolItem
        {
            public static int INDEX;
            
            public int index;
            public MySqlConnection conn;
            
            public void Release()
            {
            }

            public void Dispose()
            {
                conn?.Close();
                conn?.Dispose();
            }
        }
    }
}